/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.business.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.je.common.base.service.rpc.FunctionService;
import com.je.ibatis.SqlSessionFactoryBean;
import com.je.ibatis.extension.cache.MetaDataCacheManager;
import com.je.ibatis.extension.parse.MetaDataParse;
import com.je.ibatis.extension.plugins.JeIbatisInterceptor;
import com.je.meta.rpc.table.ResourceTableService;
import com.je.meta.service.mybatis.CommonMetaDataParse;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.apache.ibatis.plugin.Interceptor;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Set;

/**
 * 数据源配置
 */
@Configuration
public class DataSourceConfig {

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        Config config = ConfigService.getConfig("jdbc");
        DruidDataSource dataSource = createDataSource(config);
        config.addChangeListener(new DataSourceConfigListener(dataSource));
        return dataSource;
    }

    @Bean(name = "configMetaDataParse")
    public MetaDataParse configMetaDataParse(FunctionService functionService, ResourceTableService resourceTableService){
        return new CommonMetaDataParse(functionService,resourceTableService);
    }

    @Bean
    public MetaDataCacheManager metaDataCacheManager(SqlSessionFactoryBean sqlSessionFactoryBean) throws IOException {
        return sqlSessionFactoryBean.getCacheManager();
    }

    /**
     * 构建会话工厂
     *
     * @return
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource, MetaDataParse metaDataParse) throws IOException {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //设置数据源
        factoryBean.setDataSource(dataSource);
        //构建mybatis.properties
        factoryBean.setConfigurationProperties(buildMybatisProperties());
//        设置元数据解析器
        factoryBean.setMetaDataParse(metaDataParse);
        //构建插件拦截器
        Properties properties = new Properties();
        properties.setProperty("tenant.enable","false");
        Interceptor interceptor = new JeIbatisInterceptor();
        interceptor.setProperties(properties);
        factoryBean.setPlugins(interceptor);
        return factoryBean;
    }

    /**
     * 构建扫描设置
     *
     * @return
     */
    @Bean(name = "mapperScannerConfigurer")
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("com.je");
        configurer.setAnnotationClass(Mapper.class);
        return configurer;
    }

    public Properties buildMybatisProperties() throws IOException {
        Properties prop = new Properties();
        prop.load(new ClassPathResource("mybatis.properties").getInputStream());
        return prop;
    }

    protected static class DataSourceConfigListener implements ConfigChangeListener{

        private DruidDataSource dataSource;

        public DataSourceConfigListener(DruidDataSource dataSource) {
            this.dataSource = dataSource;
        }

        @Override
        public void onChange(ConfigChangeEvent changeEvent) {
            Set<String> changeKeys = changeEvent.changedKeys();
            if(changeKeys == null || changeKeys.isEmpty()){
                return;
            }
            Config config = ConfigService.getConfig(changeEvent.getNamespace());
            this.dataSource = createDataSource(config);
            try {
                this.dataSource.init();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    protected static DruidDataSource createDataSource(Config config) {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(config.getProperty("jdbc.driverClassName",""));
        //设置链接地址
        ds.setUrl(config.getProperty("jdbc.url",""));
        //设置用户名
        ds.setUsername(config.getProperty("jdbc.username",""));
        //设置密码
        ds.setPassword(config.getProperty("jdbc.password",""));
        //设置验证查询
        ds.setValidationQuery(config.getProperty("jdbc.validationQuery",""));
        //单位：秒，检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout
        ds.setValidationQueryTimeout(600);
        //初始化大小
        ds.setInitialSize(config.getIntProperty("jdbc.initialSize",30));
        ds.setMinIdle(config.getIntProperty("jdbc.minIdle",10));
        ds.setMaxActive(config.getIntProperty("jdbc.maxActive",300));
        //配置获取连接等待超时的时间
        ds.setMaxWait(60000);
        //配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
        ds.setTimeBetweenEvictionRunsMillis(60000);
        //配置一个连接在池中最小生存的时间，单位是毫秒
        ds.setMinEvictableIdleTimeMillis(200000);
        ds.setTestWhileIdle(true);
        //这里建议配置为TRUE，防止取到的连接不可用
        ds.setTestOnBorrow(false);
        ds.setTestOnReturn(false);
        //打开PSCache，并且指定每个连接上PSCache的大小
        ds.setPoolPreparedStatements(true);
        //配置每个链接最大打开的Statement数量
        ds.setMaxPoolPreparedStatementPerConnectionSize(20);
        //这里配置提交方式，默认就是TRUE，可以不用配置
        ds.setDefaultAutoCommit(true);
        //超过时间限制是否回收
        ds.setRemoveAbandoned(false);
        //回收超时时间；单位为秒。180秒=3分钟
        ds.setRemoveAbandonedTimeout(60);
        //关闭abanded连接时输出错误日志
        ds.setLogAbandoned(true);
        return ds;
    }
}

